package org.microboot.data.processor;

import org.apache.commons.collections.MapUtils;
import org.microboot.core.bean.ApplicationContextHolder;
import org.microboot.core.constant.Constant;
import org.microboot.data.utils.TransactionManagerUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import javax.sql.DataSource;
import java.util.Map;

/**
 * @author 胡鹏
 * 非默认事务管理器处理器：处理非分布式事务下，从库和其他库的独立事务管理器
 *
 * 下面是关于分布式事务实现过程中的问题、思考、学习、验证，实现等过程
 *
 * 问题：用@Bean方式手动构建固定的数据源时可以实现XA分布式事务，但在InitializingBean中动态构建不起作用？
 * 前提：XA分布式事务要成功，首先要创建DruidXADataSource，其次要将DruidXADataSource再次封装成AtomikosDataSourceBean
 * 成功原因：
 * 当时实验时，在DataConfig中创建了两个DruidXADataSource对象，分别对应两个库，并分别将其封装成了AtomikosDataSourceBean对象
 * 因此分布式事务可以成功
 * 失败原因：
 * 当时在InitializingBean中进行动态构建时，通过DruidDataSourceFactory创建数据源时，只创建了DruidXADataSource
 * 并没有将其封装成AtomikosDataSourceBean，导致构建NamedParameterJdbcTemplate时，用的也是DruidXADataSource对象
 * 导致分布式事务失败了
 * 总结：
 * 分布式事务是否成功，与创建NamedParameterJdbcTemplate时使用的DataSource对象有关，如果不是AtomikosDataSourceBean，则会失败
 *
 * 踩坑：https://www.cnblogs.com/lm970585581/p/14708807.html
 * 多数据源提交可能需要在数据库层面设置一些参数，比如：PGSQL需要在postgresql.conf中将max_prepared_transactions设置成非0
 * 一般设置成和max_connections一样大（100）就行
 *
 * 下面是解决问题过程中学习到的一些知识：
 * 1、SpringBoot启动后，会先将所有注入的Bean的名称存到一个集合中，然后再轮询去构建bean实例
 * refreshContext -> refresh ... -> finishBeanFactoryInitialization(beanFactory)
 * -> beanFactory.preInstantiateSingletons() -> beanDefinitionNames集合
 *
 * 2、beanDefinitionNames集合是有顺序的，按照SpringBoot集中扫描和注入规则依次添加进去的，根据beanDefinitionNames集合中的名称循环创建Bean实例
 * 2-1、如果希望Bean被提前创建和执行有几种方式：
 * 2-1-1、重写AbstractApplicationContext的postProcessBeanFactory方法
 * 2-1-2、自定义BeanFactoryPostProcessor或BeanDefinitionRegistryPostProcessor
 * 2-1-3、Listener，通过Event指定Listener执行的位置，具体实现可以参考：ConfigFileApplicationListener
 * 2-2、如果Spring同类型的组件（如：BeanFactoryPostProcessor，BeanPostProcessor，Listener等）希望调整初始化顺序
 * 2-2-1、可以通过实现PriorityOrdered接口来进行排序
 * 2-2-2、可以通过实现Ordered接口来进行排序
 * 2-2-3、这两个接口对于普通Bean的顺序不会产生影响
 * 2-3、如果普通Bean之间希望调整初始化顺序
 * 2-3-1、可以通过@DependsOn注解来指明依赖关系，进而控制初始化顺序
 * 2-3-2、可以通过@Order注解来指定初始化顺序，在SpringBoot核心类ConfigurationClassPostProcessor进行了排序，排序代码如下：
 * // Sort by previously determined @Order value, if applicable
 * configCandidates.sort((bd1, bd2) -> {
 *      int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
 * 		int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
 * 		return Integer.compare(i1, i2);
 * });
 *
 * 3、BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor都可以在Bean实例创建前做一些操作
 * 比如：添加自定义的BeanDefinition
 * 注意：在BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor执行时，Bean实例还没有被创建
 * 但可以通过BeanFactory的getBean()方法提前创建Bean，但可能会因为执行顺序被提前导致不可预测的问题（比如：配置信息可能读取不到等）
 *
 * 4、实现了InitializingBean接口的Bean创建和初始化之后，会在initializeBean() -> invokeInitMethods()方法中执行
 * 4-1、invokeInitMethods()是在BeanPostProcessor的postProcessBeforeInitialization和postProcessAfterInitialization之间执行的
 * 4-2、注意：虽然实现BeanPostProcessor也能实现目标，但BeanPostProcessor会在每个Bean的初始化阶段都被调用
 * 4-3、注意：这里添加的事务管理器的BeanDefinition，但并不会被立即实例化，当第一次执行事务时，会从Spring容器中去获取事务管理器的bean
 * 这是才会真正创建事务管理器的实例，并被Spring添加到singletonObjects一级缓存中，有点类似FactoryBean创建实例，是在获取时才创建的
 *
 * 5、SpringBoot的自动装配默认会创建名为transactionManager的事务管理器，但不会给transactionManager的BeanDefinition设置primary为true
 * 问题：在多数据源搭配多事务管理器的场景下，默认的@Transactional注解如果不指定事务名称会报错
 * 5-1、老版本的解决方案：
 * 手动创建名为transactionManager的事务管理器的BeanDefinition，将primary设置为true，再注册到Spring容器中，覆盖掉SpringBoot自动创建的
 * 但是DataSourcePostProcessor实例化的顺序必须在Spring的transactionManager()方法执行之前，如果顺序变了，就没有意义了
 * 而且TransactionManagerUtils是工具类，在它的方法中创建主库事务管理器也似乎也不太好
 * 5-2、新版本的解决方案：
 * 不再手动创建名为transactionManager的事务管理器，交由Spring去创建
 * 自定义DefaultTransactionManagerPostProcessor实现BeanFactoryPostProcessor接口
 * 在refreshContext -> refresh -> invokeBeanFactoryPostProcessors中会将TransactionManagerPostProcessor提前实例化出来，并执行postProcessBeanFactory()
 * 在postProcessBeanFactory()中获取transactionManager的BeanDefinition，并将primary设置为true
 *
 * 6、动态创建数据源以及事务管理器都是经过测试了的，但这里其实只创建了事务管理器的BeanDefinition
 * 实际上事务管理器的bean对象是在事务第一次被使用时才被Spring临时创建并添加到singletonObjects一级缓存的
 */
public class NonDefaultTransactionManagerPostProcessor implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        //主库数据源
        DataSource masterDataSource = ApplicationContextHolder.getBean(Constant.MASTER_DATA_SOURCE, DataSource.class);
        //从库数据源
        DataSource slavesDataSource = ApplicationContextHolder.getBean(Constant.SLAVES_JDBC_TEMPLATE, NamedParameterJdbcTemplate.class)
                .getJdbcTemplate().getDataSource();

        //如果从库数据源和主库数据源是同一个，则不用创建事务管理器
        if (slavesDataSource != masterDataSource) {
            //从库事务管理器
            TransactionManagerUtils.dynamicTransactionManager(slavesDataSource);
        }

        //其他库事务
        Map<String, NamedParameterJdbcTemplate> othersJdbcTemplateMap = ApplicationContextHolder.getBean(Constant.OTHERS_JDBC_TEMPLATE, Map.class);
        if (MapUtils.isEmpty(othersJdbcTemplateMap)) {
            return;
        }
        for (NamedParameterJdbcTemplate namedParameterJdbcTemplate : othersJdbcTemplateMap.values()) {
            if (namedParameterJdbcTemplate == null) {
                continue;
            }
            //其他库事务管理器
            TransactionManagerUtils.dynamicTransactionManager(namedParameterJdbcTemplate.getJdbcTemplate().getDataSource());
        }
    }
}
